#ifndef HSE_SEARCH_ENGINE
#define HSE_SEARCH_ENGINE
#include <string>
#include <iostream>
#include <algorithm>
#include <stdlib.h>
#include <stdio.h>
#include "hse_common.h"
#include "hse_types.h"
#include "document_meta_datas.h"
#include "structured_index.h"
#include "search.h"
#include "search_engine.h"

namespace hse {
using std::string;
using std::cout;
using std::endl;


SearchEngine::SearchEngine(string& index_path) {
	AddIndex(index_path);
}

void SearchEngine::AddIndex(string &index_path) {
	string meta_file = index_path + string(FILE_META);
	FILE *f = fopen(meta_file.c_str(), "rb");
	hse::DocumentMetaDatas::Instance().Load(f);
	fclose(f);
	
	string index_file = index_path + string(FILE_POSTING);
	StructuredIndex structured_index(inverted_list_);
	f = fopen(index_file.c_str(), "rb");
	structured_index.Load(f);
}

//return search time
int SearchEngine::RunSearch(string& query, int start, int count, vector<SearchResult>& result, int *total, int segment_length) {
	if (segment_length > 200) {
		segment_length = 200;
	}
	Search search(inverted_list_);
	Search::QueryResults results;
	search.RunQuery(query, start, count, results);
	*total = results.total;

	//transform
	DocumentMetaDatas& meta_data= hse::DocumentMetaDatas::Instance();
	int term_size = results.terms.size();
	int *q = new int[term_size];  //pointer for calculate best display place
	int *best_place = new int[term_size];
	for(size_t i = 0; i < results.docs.size(); ++i) {
		LOG_INFO(cout << "doc_id:" << results.docs[i].doc_id << ",score:" << results.docs[i].score
			 << ", positions:");
		int ps, pos_count = results.docs[i].positions.size();
		for(size_t j = 0; j < pos_count; ++j) {
			int ps = results.docs[i].positions[j];
			if (ps == -1) continue;
			LOG_INFO(cout << ps << ",");
		}
		LOG_INFO(cout << endl);
		
		SearchResult r;
		if (results.terms.size() == 1) {
			int ps = results.docs[i].p_positions[0]->positions[0] - 20;
			if (ps < 0) ps = 0;
			string content = hse::DocumentMetaDatas::Instance().GetContent(results.docs[i].doc_id, ps, segment_length);
			r.segment = string_term_em(content, results.terms);
			r.segment.append("..@1..");
			r.doc_name = string_term_em(meta_data.GetMetaData(results.docs[i].doc_id)->name, results.terms);
			r.doc_id = results.docs[i].doc_id;
			result.push_back(r);
		} else {
			int start, end, best_len;
			int min = INT_MAX, max = INT_MIN, minp, real_min, j;
			bool finished = true;
			real_min = INT_MAX;
			vector<const DocPositions*>& pos = results.docs[i].p_positions;
			for(j = 0; j < term_size; ++j) {
				q[j] = 0;
				if(pos[j]->positions.size() > 1 && pos[j]->positions[0] < min) {
					min = pos[j]->positions[0];
					minp = j;
				}

				if (pos[j]->positions[0] < real_min) {
					real_min = pos[j]->positions[0];
					start = real_min;
				}

				if(pos[j]->positions[0] > max) {
					max = pos[j]->positions[0];
					end = max;
				}
				if(q[j] < pos[j]->positions.size() - 1) finished = false;
			}
			best_len = max - real_min;
			if (best_len > segment_length - 20) {
				for(int k = 0; k < term_size; ++k)
						best_place[k] = pos[k]->positions[q[k]];
			}
			while(!finished && best_len > segment_length - 20) {
				finished = true;
				for(j = 0; j < term_size; ++j) {
					if (q[j] < pos[j]->positions.size() - 1) {
						finished = false;
						break;
					}	
				}
				if (finished) break;
				//replace smallest
				q[minp]++;
				min = INT_MAX;
				real_min = INT_MAX;
				for(j = 0; j < term_size; ++j) {
					if(q[j] < pos[j]->positions.size() - 1 && pos[j]->positions[q[j]] < min) {
						min = pos[j]->positions[q[j]];
						minp = j;
					}
					if(pos[j]->positions[q[j]] < real_min) {
						real_min = pos[j]->positions[q[j]];
					}
					if(pos[j]->positions[q[j]] > max) {
						max = pos[j]->positions[q[j]];
					}
				}
				if (max - real_min < best_len) {
					start = real_min;
					end = max;
					best_len = max - real_min;
					if (best_len > segment_length - 20) {
						for(int k = 0; k < term_size; ++k)
							best_place[k] = pos[k]->positions[q[k]];
					}
				}
			}
			if (best_len <= segment_length - 20) { //one segment is ok
				string content = meta_data.GetContent(results.docs[i].doc_id, start, segment_length);
				r.segment = string_term_em(content, results.terms);
				r.segment.append("...");
				r.doc_name = string_term_em(meta_data.GetMetaData(results.docs[i].doc_id)->name, results.terms);
				r.doc_id = results.docs[i].doc_id;
				result.push_back(r);
			} else { //needs more segments
				r.doc_name = string_term_em(meta_data.GetMetaData(results.docs[i].doc_id)->name, results.terms);
				r.doc_id = results.docs[i].doc_id;

				std::sort(best_place, &best_place[term_size]);
				int block_len = segment_length / term_size;
				int try_end;
				start = -1;
				for (j = 0; j < term_size; ++j) {
					if (start == -1) {
						start = best_place[j];
						try_end = start + block_len;
						continue;
					}
					if (try_end > best_place[j] + results.terms[j].size()) {
						try_end +=  block_len;
					} else {
						r.segment.append(string_term_em(meta_data.GetContent(results.docs[i].doc_id, start, try_end - start), results.terms));
						r.segment.append("...");
						start = best_place[j];
						try_end = start + block_len;
					}
				}
				if (start != -1) {
					r.segment.append(string_term_em(meta_data.GetContent(results.docs[i].doc_id, start, try_end - start), results.terms));
					r.segment.append("...");
				}
				result.push_back(r);
			}
		}

	}
	delete q;
	delete best_place;
	return results.time_used;
}

}


#endif